package com.hongshu.idle.service;

import cn.hutool.core.collection.CollectionUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hongshu.idle.service.IRecommendProductService;
import com.hongshu.common.core.constant.NoteConstant;
import com.hongshu.common.core.enums.AuditStatusEnum;
import com.hongshu.common.core.utils.bean.BeanUtils;
import com.hongshu.idle.domain.entity.IdleCollection;
import com.hongshu.idle.domain.entity.IdleProduct;
import com.hongshu.idle.domain.vo.IdleProductVO;
import com.hongshu.idle.mapper.idle.IdleCollectionMapper;
import com.hongshu.idle.mapper.idle.IdleProductMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.mahout.cf.taste.impl.model.jdbc.MySQLJDBCDataModel;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.UncenteredCosineSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;


/**
 * ES
 *
 * @author: hongshu
 */
@Service
@Slf4j
public class RecommendProductServiceImpl extends ServiceImpl<IdleCollectionMapper, IdleCollection> implements IRecommendProductService {

    @Autowired
    private DataSource dataSource;
    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private IdleCollectionMapper collectionMapper;
    @Autowired
    private IdleProductMapper productMapper;


    /**
     * 推荐商品
     *
     * @param userId 用户ID
     */
    @Override
    public List<IdleProductVO> getRecommendProduct(long userId) {
        List<IdleProductVO> idleProductVOList;
        try {
            // Step 1: 创建数据模型：使用 MySQLJDBCDataModel 从数据库表加载数据。
            // 有一个表 web_like_or_collection，存储用户对商品的交互数据。
            DataModel model = new MySQLJDBCDataModel(
                    dataSource,
                    "idle_collection",      // Table name
                    "uid",                  // User ID column
                    "collection_id",        // Blog ID column
                    "type",                 // Rating column (Here we use interaction type as rating)
                    "create_time"           // Timestamp column (optional)
            );
            // Step 2: 计算相似度：使用皮尔逊相关系数计算用户之间的相似度。未中心化的余弦相似度
            UserSimilarity similarity = new UncenteredCosineSimilarity(model);
            // Step 3: 定义邻居：使用 NearestNUserNeighborhood 定义用户邻居。
            NearestNUserNeighborhood neighborhood = new NearestNUserNeighborhood(10, similarity, model);
            //  Step 4: 创建推荐器：基于用户的相似度，创建基于用户的推荐器。
            Recommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity);
            // Step 5: 生成推荐：为指定用户生成推荐笔记列表。
            List<RecommendedItem> recommendations = recommender.recommend(userId, 10);
            List<Long> recommendedProductIds = recommendations.stream()
                    .map(RecommendedItem::getItemID)
                    .collect(Collectors.toList());
            // Step 6: 获取推荐笔记详情：从 Elasticsearch 中获取推荐笔记。
            SearchRequest searchRequest = this.buildProductSearchRequest(recommendedProductIds);
            // Step 7: 筛选出所需的数据。
            SearchResponse<IdleProductVO> searchResponse = elasticsearchClient.search(searchRequest, IdleProductVO.class);
            idleProductVOList = searchResponse.hits().hits().stream()
                    .map(Hit::source)
                    .filter(product -> product.getStatus() == 0)
                    .collect(Collectors.toList());
            // 随机排序
            Collections.shuffle(idleProductVOList, new Random());
        } catch (Exception e) {
            log.error("获取推荐商品时发生异常：：", e);
            return Collections.emptyList();
        }
        return idleProductVOList;
    }

    /**
     * 获取推荐商品列表
     */
    private SearchRequest buildProductSearchRequest(List<Long> recommendedProductIds) {
        // 推荐列表为空，默认随机加载100条数据
        if (CollectionUtil.isEmpty(recommendedProductIds)) {
            return SearchRequest.of(s -> s
                    .index(NoteConstant.PRODUCT_INDEX)
                    .size(100)
            );
        } else {
            // 推荐列表不为空，加载推荐数据
            List<FieldValue> fieldValues = recommendedProductIds.stream()
                    .map(FieldValue::of)
                    .collect(Collectors.toList());
            return SearchRequest.of(s -> s
                    .index(NoteConstant.PRODUCT_INDEX)
                    .query(q -> q
                            .terms(t -> t
                                    .field("id")
                                    .terms(terms -> terms.value(fieldValues))
                            )
                    )
                    .size(recommendedProductIds.size())
            );
        }
    }

    /**
     * 从MySQL获取推荐商品
     *
     * @param userId      用户ID
     * @param currentPage 当前页
     * @param pageSize    页大小
     * @return 分页结果
     */
    @Override
    public Page<IdleProductVO> getRecommendProductFromMysql(String userId, long currentPage, long pageSize) {
        Page<IdleProductVO> page = new Page<>(currentPage, pageSize);

        // 获取用户点赞和收藏的笔记ID列表
        List<String> collectedProductIds = collectionMapper.selectList(
                        new QueryWrapper<IdleCollection>()
                                .eq("uid", userId)
                                .in("type", Arrays.asList(1, 3)) // 1：点赞图片 3：收藏图片
                                .select("collection_id")
                ).stream()
                .map(IdleCollection::getCollectionId)
                .collect(Collectors.toList());
        // 获取用户点赞/收藏的笔记的作者ID列表
        List<String> authorIds = Collections.emptyList();
        if (!collectedProductIds.isEmpty()) {
            authorIds = productMapper.selectList(
                            new QueryWrapper<IdleProduct>()
                                    .in("id", collectedProductIds)
                                    .select("DISTINCT uid")
                    ).stream()
                    .map(IdleProduct::getUid)
                    .collect(Collectors.toList());
        }
        // 获取用户点赞/收藏的笔记的分类ID列表
        List<String> categoryIds = Collections.emptyList();
        if (!collectedProductIds.isEmpty()) {
            categoryIds = productMapper.selectList(
                            new QueryWrapper<IdleProduct>()
                                    .in("id", collectedProductIds)
                                    .select("DISTINCT cpid")
                    ).stream()
                    .map(IdleProduct::getCpid)
                    .collect(Collectors.toList());
        }
        // 构建查询条件
        QueryWrapper<IdleProduct> queryWrapper = new QueryWrapper<IdleProduct>()
                .eq("audit_status", AuditStatusEnum.PASS.getCode());
        // 如果有用户行为数据，基于用户偏好推荐
        if (!authorIds.isEmpty() || !categoryIds.isEmpty()) {
            List<String> finalAuthorIds = authorIds;
            List<String> finalCategoryIds = categoryIds;
            queryWrapper.and(wrapper -> {
                if (!finalAuthorIds.isEmpty()) {
                    wrapper.or().in("uid", finalAuthorIds);
                }
                if (!finalCategoryIds.isEmpty()) {
                    wrapper.or().in("cid", finalCategoryIds);
                }
            });
        }
        // 排除用户已经点赞/收藏的笔记
        if (!collectedProductIds.isEmpty()) {
            queryWrapper.notIn("id", collectedProductIds);
        }
        // 排除用户自己的笔记
        queryWrapper.ne("uid", userId);
        // 按点赞数、收藏数、创建时间排序
        queryWrapper.orderByDesc("like_count")
                .orderByDesc("update_time");

        // 执行分页查询
        Page<IdleProduct> notePage = productMapper.selectPage(new Page<>(currentPage, pageSize), queryWrapper);
        // 转换结果
        page.setCurrent(notePage.getCurrent());
        page.setSize(notePage.getSize());
        page.setTotal(notePage.getTotal());
        page.setRecords(notePage.getRecords().stream()
                .map(this::convertToSearchVo)
                .collect(Collectors.toList()));
        return page;
    }

    /**
     * 将WebNote实体转换为NoteSearchVo
     */
    private IdleProductVO convertToSearchVo(IdleProduct note) {
        IdleProductVO vo = new IdleProductVO();
        BeanUtils.copyProperties(note, vo);
        return vo;
    }
}
